﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;

namespace MipsMachine {
	public partial class Editor : Form {
		[System.Runtime.InteropServices.DllImport("user32")]
		public static extern bool LockWindowUpdate(IntPtr hWndLock);

		private static readonly Regex Tokenizer = new Regex(MipsAssembler.TokenizerRegex, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);

		public const string ExportFilter = "Binary files (*.bin)|*.bin|All files (*.*)|*.*";
		public const string SaveFilter = "Assembler source (*.asm)|*.asm|All files (*.*)|*.*";
		MipsSourceFile current_file;
		MipsProject project;
		MipsMachine machine;

		private string OpenFile;
		private bool changed = false;
		private bool checkingSyntax;

		public Editor() {
			InitializeComponent();
			project = new MipsProject("untitled.mpj");
			current_file = project.Directory.AddNewFile("untitled.asm");
			current_file.Code = @"addi $v0, $0, 4
addi $a0, $0, hello_str
syscall

addi $v0, $0, 10
syscall

" + "hello_str: db \"Hello World!\\nThanks and goodbye.\"";

			AssemblyCode.Text = current_file.Code;
			var lines = AssemblyCode.Lines;
			for (int i = 0; i < lines.Length; i++)
				SyntaxHighlight(i);
		}

		public void SyntaxHighlight(int line) {
			if (!checkingSyntax)
				SyntaxHighlight(line, true);
		}

		public void SyntaxHighlight(int line, bool lockUpdate) {
			try {
				if (lockUpdate)
					LockWindowUpdate(AssemblyCode.Handle);
				int start = AssemblyCode.GetFirstCharIndexFromLine(line);
				string[] lines = AssemblyCode.Lines;
				if (line < lines.Length) {
					string l = lines[line];
					var m = Tokenizer.Match(l);
					int sel_start = AssemblyCode.SelectionStart;
					int sel_length = AssemblyCode.SelectionLength;
					AssemblyCode.SelectionStart = start;
					AssemblyCode.SelectionLength = l.Length;
					AssemblyCode.SelectionColor = Color.Black;
					while (m.Success) {
						if (m.Groups["Comment"].Success) {
							AssemblyCode.SelectionStart = m.Index + start;
							AssemblyCode.SelectionLength = m.Length;
							AssemblyCode.SelectionColor = Color.Green;
						}
						else if (m.Groups["Preprocessor"].Success) {
							AssemblyCode.SelectionStart = m.Index + start;
							AssemblyCode.SelectionLength = m.Length;
							AssemblyCode.SelectionColor = Color.DarkBlue;
						}
						else if (m.Groups["Identifier"].Success) {
							AssemblyCode.SelectionStart = m.Index + start;
							AssemblyCode.SelectionLength = m.Length;
							AssemblyCode.SelectionColor = Color.Blue;
						}
						else if (m.Groups["Number"].Success) {
							AssemblyCode.SelectionStart = m.Index + start;
							AssemblyCode.SelectionLength = m.Length;
							AssemblyCode.SelectionColor = Color.SlateGray;
						}
						else if (m.Groups["Register"].Success || m.Groups["MemRegister"].Success) {
							AssemblyCode.SelectionStart = m.Index + start;
							AssemblyCode.SelectionLength = m.Length;
							AssemblyCode.SelectionColor = Color.DarkRed;
						}
						m = m.NextMatch();
					}
					AssemblyCode.SelectionStart = sel_start;
					AssemblyCode.SelectionLength = sel_length;
				}

			}
			finally {
				// Force unlocking window update
				if (lockUpdate)
					LockWindowUpdate(IntPtr.Zero);
			}
		}

		public void SyntaxHighlight() {
			var lines = AssemblyCode.Lines;
			LockWindowUpdate(AssemblyCode.Handle);
			checkingSyntax = true;
			for (int i = 0; i < lines.Length; i++) {
				SyntaxHighlight(i, false);
			}
			LockWindowUpdate(IntPtr.Zero);
			checkingSyntax = false;
		}

		private void AssemblyCode_TextChanged(object sender, EventArgs e) {
			current_file.Code = AssemblyCode.Text;
			SyntaxHighlight(AssemblyCode.GetLineFromCharIndex(AssemblyCode.SelectionStart));
			changed = true;
		}

		private void ProgramCompile_Click(object sender, EventArgs e) {
			compile();
		}

		private void ProgramRun_Click(object sender, EventArgs e) {
			runProgram();
		}

		private void AssemblyCode_SelectionChanged(object sender, EventArgs e) {
			updateLineAndRow();
		}

		private void Output_TextChanged(object sender, EventArgs e) {
			Output.Select(Output.Text.Length, 0);
			Output.ScrollToCaret();
		}

		private void FileOpen_Click(object sender, EventArgs e) {
			openFile();
		}

		private void FileCut_Click(object sender, EventArgs e) {
			AssemblyCode.Cut();
			SyntaxHighlight();
		}

		private void FileCopy_Click(object sender, EventArgs e) {
			AssemblyCode.Copy();
		}

		private void FilePaste_Click(object sender, EventArgs e) {
			AssemblyCode.Paste();
			SyntaxHighlight();
		}

		private void undoToolStripMenuItem_Click(object sender, EventArgs e) {
			AssemblyCode.Undo();
			SyntaxHighlight();
		}

		private void redoToolStripMenuItem_Click(object sender, EventArgs e) {
			AssemblyCode.Redo();
			SyntaxHighlight();
		}

		private void ProgramImportBin_Click(object sender, EventArgs e) {
			importBin();
		}

		private void OpenDlg_FileOk(object sender, CancelEventArgs e) {

		}

		private void FileExit_Click(object sender, EventArgs e) {
			Application.Exit();
		}

		private void FileNew_Click(object sender, EventArgs e) {
			newFile();
		}

		private void FileSave_Click(object sender, EventArgs e) {
			save();
		}

		private void FileSaveAs_Click(object sender, EventArgs e) {
			saveAs();
		}

		private void ProgramDebug_Click(object sender, EventArgs e) {
			debug();
		}

		private void ProgramExportBin_Click(object sender, EventArgs e) {
			exportBinFile();
		}


		private void aboutToolStripMenuItem_Click(object sender, EventArgs e) {
            //MessageBox.Show("µass - MIPS Assembler and Simulation software\nMade by:\n\tHenning Moe\n\tAksel Kvitberg\nReleased under GNU public lisence v3.\nReport all bugs and get source code at:\nhttp://code.google.com/p/myass/");
            new AboutBox1().ShowDialog();

		}

		private void ExitProgram() {
			//Check if file is changed before exiting
			if (changed) {
				DialogResult result = MessageBox.Show("Exiting", "Do you want to save?", MessageBoxButtons.YesNoCancel);
				if (result == DialogResult.Yes) {
					save();
					Application.Exit();
				} if (result == DialogResult.No) {
					Application.Exit();
				} if (result == DialogResult.Cancel) {

				}
			}
			else {
				Application.Exit();
			}
		}

		private void newFile() {
			AssemblyCode.Text = "";
			project.Program = null; //Will this cause a memory leak? Or is it even neccesary?
			OpenFile = null;
		}

		private void openFile() {
			OpenDlg.Filter = SaveFilter;
			if (OpenDlg.ShowDialog() == DialogResult.OK) {
				openFile(OpenDlg.FileName);
			}
		}

		private void openFile(string filename) {
			OpenFile = filename;
			string text = System.IO.File.ReadAllText(OpenFile);
			AssemblyCode.Text = text;
			SyntaxHighlight();
		}

		private void save() { // Don't save if there has not been any changes
			if (OpenFile == null)
				saveAs();
			else
				save(OpenFile);
		}

		private void saveAs() { // Change regardless of changes.
			SaveDlg.Filter = SaveFilter;
			if (SaveDlg.ShowDialog() == DialogResult.OK) {
				OpenFile = SaveDlg.FileName;
				save(OpenFile);
			}
		}

		private void save(string filename) { // Just the general save code
			changed = false;
			System.IO.File.WriteAllText(filename, AssemblyCode.Text);
		}

		private void importBin() {
			OpenDlg.Filter = ExportFilter;
			if (OpenDlg.ShowDialog() == DialogResult.OK) {
                newFile();
				string[] s = MipsDisassembler.disassembleFile(OpenDlg.FileName);
				AssemblyCode.Lines = s;
				SyntaxHighlight();
			}
		}

		private void exportBinFile() {
			exportBin.Filter = ExportFilter;
			if (exportBin.ShowDialog() == DialogResult.OK) {
				compile(exportBin.FileName);
			}
		}

		public void compile(String filename) {
			string tempOutput = project.Output;
			project.Output = filename;
			compile();
			project.Output = tempOutput;
		}

		public void compile() {
			int errors = 0, warnings = 0;
			System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
			Output.Text += "Compiling...\n-----------------------------\n";
			st.Start();

			project.Compile((fn, prog) => {
				LineAndRowLabel.Text = fn; StatusProgressBar.Value = prog;
			},
			item => {
				string txt = item.ToString();
				int pos = Output.TextLength;
				Output.Text += txt + "\n";

				if (item.Type == CompilerLogEntryType.Error) {
					LockWindowUpdate(Output.Handle);
					Output.Select(pos, txt.Length);
					Output.SelectionColor = Color.Red;
					Output.Select(Output.TextLength, 0);
					LockWindowUpdate(IntPtr.Zero);
					errors++;
				}
				else if (item.Type == CompilerLogEntryType.Warning) {
					LockWindowUpdate(Output.Handle);
					Output.Select(pos, txt.Length);
					Output.SelectionColor = Color.Blue;
					Output.Select(Output.TextLength, 0);
					LockWindowUpdate(IntPtr.Zero);

					warnings++;
				}
			}
			);
			st.Stop();
			Output.Text += "Compile " + (errors > 0 ? "failed" : "succeded") + " in ";
			if (st.Elapsed.Hours != 0)
				Output.Text += " " + st.Elapsed.Hours + "h";
			if (st.Elapsed.Minutes != 0)
				Output.Text += " " + st.Elapsed.Minutes + "m";
			if (st.Elapsed.Seconds != 0)
				Output.Text += " " + st.Elapsed.Seconds + "s";
			if (st.Elapsed.Milliseconds != 0)
				Output.Text += " " + st.Elapsed.Milliseconds + "ms";

			Output.Text += " with " + errors + (errors == 1 ? " error and " : " errors and ") + warnings + (warnings == 1 ? " warning.\r\n" : " warnings.\r\n");
			Output.Select(Output.Text.Length, 0);
			Output.ScrollToCaret();
		}

		private void runProgram() {
			if (project.Program != null && machine == null) {
				StatusProgressBar.Style = ProgressBarStyle.Marquee;
				machine = new MipsMachine(project.Program);
				machine.Callback += value => { Output.Text += value; };
				machine.Initialize(4096);
				machine.Run();
				Output.Text += "\r\n-----------------------------\nRun completed\r\n";
				Output.Select(Output.Text.Length, 0);
				Output.ScrollToCaret();
				StatusProgressBar.Style = ProgressBarStyle.Continuous;
				machine = null;
			}
		}

		private void debug() {
			if (project.Program != null) {
				Disassembler dis = new Disassembler(project.Program);
				dis.ShowDialog();
			}
		}

		private void updateLineAndRow() {
			int selectionStart = AssemblyCode.SelectionStart;
			int lineStart = AssemblyCode.GetFirstCharIndexOfCurrentLine();
			int row = selectionStart - lineStart + 1;

			int line = AssemblyCode.GetLineFromCharIndex(selectionStart) + 1;
			LineAndRowLabel.Text = line + ":" + row;
		}

		private void stopToolStripMenuItem_Click(object sender, EventArgs e) {
			machine.Stop();
		}
	}
}